/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <linux/completion.h>
#include <linux/kthread.h>
#include <linux/hardirq.h>
#include <linux/slab.h>

#include "mrp_mrpdu.h"
#include "mrp_private.h"
#include "mrp_attribute.h"
#include "mrp_leave_all.h"
#include "mrp_state_machine.h"
#include "mrp_message_processing_thread.h"
#include "../include/mrp_attribute_properties.h"

static struct mrp_message_processing_thread * mrp_message_processing_thread = NULL;
static spinlock_t create_destroy_lock;

// ===============================================================================================================
// ===============================================================================================================
struct mrp_message
{
    struct mrp * mrp;
    
    struct list_head list;
    
    int message_id;
    void * data;
    void (*data_destroy_function)(void ** data);
};

static void mrp_message_destroy (struct mrp_message ** mrp_message);

static struct mrp_message * mrp_message_init (struct mrp * mrp, int message_id, void * data, void (*data_destroy_function)(void ** data))
{
    struct mrp_message * mrp_message = NULL;

    if ( 	  (message_id < MRP_MESSAGE_ID_MIN || message_id > MRP_MESSAGE_ID_MAX)
        ||  ! (mrp_message = kzalloc (sizeof(struct mrp_message), MRP_GET_CONTEXT_ALLOC_FLAG))		)
    {
        mrp_message_destroy (&mrp_message);
        return NULL;
    }
    
    mrp_message->mrp = mrp;
    mrp_message->message_id = message_id;
    mrp_message->data = data;
    mrp_message->data_destroy_function = data_destroy_function;

    return mrp_message;
}

static void mrp_message_destroy (struct mrp_message ** mrp_message)
{
    struct mrp_message * mes;

    mes = *mrp_message;

    if (mes)
    {
        if (mes->data_destroy_function && mes->data)
            mes->data_destroy_function (&mes->data);
        kfree (mes);
        *mrp_message = NULL;
    }
}
// ===============================================================================================================
// ===============================================================================================================

struct message_processing_thread_function_data
{
    message_processing_thread_fucntion message_processing_thread_fucntion;
    void * data;

    /* no need to init this - done in message processing thread */
    struct completion completion;
    int ret_val;
};

static void work_function (struct work_struct * work)
{
    struct mrp_message * mrp_message;
    struct mrp_attribute_properties * mrp_attribute_properties;
    struct mrp_message_processing_thread * mrp_message_processing_thread;
    struct message_processing_thread_function_data * message_processing_thread_function_data;
    unsigned long flags;
    
    mrp_message_processing_thread = container_of(work, struct mrp_message_processing_thread, work);

    spin_lock_irqsave(&mrp_message_processing_thread->message_list_lock, flags);

    while (mrp_message_processing_thread->message_list.next != &mrp_message_processing_thread->message_list)
    {
        mrp_message = list_entry(mrp_message_processing_thread->message_list.next, struct mrp_message, list);
        list_del (mrp_message_processing_thread->message_list.next);
        spin_unlock_irqrestore (&mrp_message_processing_thread->message_list_lock, flags);

        switch (mrp_message->message_id)
        {
            case MRP_MESSAGE_ID_PROCESS_INCOMING_MRPDU:
            {
                if (mrp_perform_receive_mrpdu(mrp_message->mrp, (struct mrp_mrpdu *) mrp_message->data))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_PROCESS_INCOMING_MRPDU mrp_perform_receive_mrpdu FAILED");
                break;
            }
            case MRP_MESSAGE_ID_LEAVE_ALL_TIMER_EXPIRE:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_message->data, MRP_PROTO_EVENT_leavealltimer))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_LEAVE_ALL_TIMER_EXPIRE mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_TIMER_EXPIRE:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_message->data, MRP_PROTO_EVENT_periodictimer))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_TIMER_EXPIRE mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_LEAVE_TIMER_EXPIRE:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) ((struct mrp_attribute *)mrp_message->data)->mrp_registrar, MRP_PROTO_EVENT_leavetimer))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_LEAVE_TIMER_EXPIRE mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_JOINTIMER_EXPIRE:
            {
                if (mrp_perform_jointimer_expire (mrp_message->mrp))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_JOINTIMER_EXPIRE mrp_perform_jointimer_expire FAILED");
                break;
            }
            case MRP_MESSAGE_ID_PERIODIC_ENABLE:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_message->data, MRP_PROTO_EVENT_periodicEnable))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_PERIODIC_ENABLE mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_PERIODIC_DISABLE:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_message->data, MRP_PROTO_EVENT_periodicDisable))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_PERIODIC_DISABLE mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_LEAVE_ALL_BEGIN:
            case MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_BEGIN:
            {
                if (mrp_state_machine_receive_protocol_event ((struct mrp_state_machine *) mrp_message->data, MRP_PROTO_EVENT_Begin))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_LEAVE_ALL_BEGIN MRP_MESSAGE_ID_PERIODIC_TRANSMISSION_BEGIN mrp_state_machine_receive_protocol_event FAILED");
                break;
            }
            case MRP_MESSAGE_ID_MAD_JOIN_REQUEST:
            {
                mrp_attribute_properties = (struct mrp_attribute_properties *) mrp_message->data;
                if (mrp_perform_mad_join_request (mrp_message->mrp, mrp_attribute_properties->attribute_type, mrp_attribute_properties->attribute_value, mrp_attribute_properties->four_packed_type, mrp_attribute_properties->new_attribute))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_MAD_JOIN_REQUEST mrp_perform_mad_join_request FAILED");
                break;
            }
            case MRP_MESSAGE_ID_MAD_LEAVE_REQUEST:
            {
                mrp_attribute_properties = (struct mrp_attribute_properties *) mrp_message->data;
                if (mrp_perform_mad_leave_request (mrp_message->mrp, mrp_attribute_properties->attribute_type, mrp_attribute_properties->attribute_value, mrp_attribute_properties->four_packed_type))
                    MRP_PRINT_INFO ("%s\n", "MRP_MESSAGE_ID_MAD_LEAVE_REQUEST mrp_perform_mad_leave_request FAILED");                    
                break;
            }
            case MRP_MESSAGE_ID_CALL_FUNCTION_ON_MESSAGE_THREAD:
            {
                message_processing_thread_function_data = (struct message_processing_thread_function_data *) mrp_message->data;
                message_processing_thread_function_data->ret_val = message_processing_thread_function_data->message_processing_thread_fucntion (message_processing_thread_function_data->data);
                complete (&message_processing_thread_function_data->completion);
                break;
            }
            case MRP_LEAVE_ALL_ATTRIBUTES:
            {
                if (mrp_perform_leave_request_on_all_attributes(mrp_message->mrp))
                    MRP_PRINT_INFO ("%s\n", "MRP_LEAVE_ALL_ATTRIBUTES mrp_perform_leave_request_on_all_attributes FAILED");
                break;
            }
            default:
            {
                MRP_PRINT_INFO ("%s %i\n", "MRP Message with unknown message ID received:", mrp_message->message_id);
                break;
            }
        }

        mrp_message_destroy (&mrp_message);

        spin_lock_irqsave (&mrp_message_processing_thread->message_list_lock, flags);
    }

    spin_unlock_irqrestore(&mrp_message_processing_thread->message_list_lock, flags);
}

static void destroy (void)
{
    if (mrp_message_processing_thread)
    {
        if (mrp_message_processing_thread->workqueue)
        {
            flush_workqueue(mrp_message_processing_thread->workqueue);
            destroy_workqueue (mrp_message_processing_thread->workqueue);
        }
        
        kfree (mrp_message_processing_thread);
        mrp_message_processing_thread = NULL;
    }
}

void mrp_message_processing_thread_init_lock(void)
{
    spin_lock_init(&create_destroy_lock);
}

struct mrp_message_processing_thread * mrp_message_processing_thread_get_instance(void)
{
    spin_lock(&create_destroy_lock);
    
    if (mrp_message_processing_thread)
        mrp_message_processing_thread->ref_count++;
    else
    {
        if ( 	! (mrp_message_processing_thread = kzalloc(sizeof(struct mrp_message_processing_thread), MRP_GET_CONTEXT_ALLOC_FLAG))
            ||  ! (mrp_message_processing_thread->workqueue = create_singlethread_workqueue("MRPMessageProcessing")) )
        {
            destroy();
        }
        else
        {
            INIT_WORK(&mrp_message_processing_thread->work, work_function);
            INIT_LIST_HEAD(&mrp_message_processing_thread->message_list);
            spin_lock_init(&mrp_message_processing_thread->message_list_lock);
            mrp_message_processing_thread->ref_count = 0;
        }
    }
    
    spin_unlock(&create_destroy_lock);
    return mrp_message_processing_thread;
}

void mrp_message_processing_thread_release_instance(void)
{
    spin_lock(&create_destroy_lock);
    
    if (mrp_message_processing_thread)
    {
        while(work_pending(&mrp_message_processing_thread->work))
            mdelay(20);
            
        if ((--(mrp_message_processing_thread->ref_count)) == 0)
            destroy();
    }
    
    spin_unlock(&create_destroy_lock);
}

int mrp_message_processing_thread_submit_message (struct mrp_message_processing_thread * mrp_message_processing_thread, struct mrp * mrp, int message_id, void * data, void (*data_destroy_function)(void ** data))
{
    int ret;
    struct mrp_message * mrp_message;
    struct message_processing_thread_function_data * message_processing_thread_function_data;
    
    if ((mrp_message = mrp_message_init (mrp, message_id, data, data_destroy_function)))
    {
        spin_lock (&(mrp_message_processing_thread->message_list_lock));
        list_add_tail (&mrp_message->list, &mrp_message_processing_thread->message_list);
        spin_unlock (&(mrp_message_processing_thread->message_list_lock));
        queue_work (mrp_message_processing_thread->workqueue, &mrp_message_processing_thread->work);
        if (message_id == MRP_MESSAGE_ID_CALL_FUNCTION_ON_MESSAGE_THREAD)
        {
            message_processing_thread_function_data = (struct message_processing_thread_function_data *) data;
            init_completion (&message_processing_thread_function_data->completion);
            if ((ret = wait_for_completion_interruptible (&message_processing_thread_function_data->completion)))
                return ret;
            return message_processing_thread_function_data->ret_val;
        }
        return 0;
    }
    else
    {
        return -ENOMEM;
    }
}

int mrp_message_processing_thread_call_function (struct mrp_message_processing_thread * mrp_message_processing_thread, struct mrp * mrp, message_processing_thread_fucntion message_processing_thread_fucntion, void * data)
{
    struct message_processing_thread_function_data message_processing_thread_function_data;
    message_processing_thread_function_data.message_processing_thread_fucntion = message_processing_thread_fucntion;
    message_processing_thread_function_data.data = data;
    return mrp_message_processing_thread_submit_message (mrp_message_processing_thread, mrp, MRP_MESSAGE_ID_CALL_FUNCTION_ON_MESSAGE_THREAD, &message_processing_thread_function_data, NULL);
}
